#include "record.h"

#include <stddef.h>

#include <stdio.h>

#include <jni.h>

#include "tokenizer.h"

#include "tags.h"

#include "log/log.h"

/*
package com.netease.nimlib.search.model;

public class NIMIndexRecord {
	public long type;

	public long subtype;

	public long dataid;

	public String id;

	public long time;
	
	public String content;

	public int count;
}
*/

static jclass class_record;
static jfieldID field_type;
static jfieldID field_subtype;
static jfieldID field_dataid;
static jfieldID field_id;
static jfieldID field_time;
static jfieldID field_content;
static jfieldID field_count;

void record_init(JNIEnv* env) {
	class_record = env->FindClass("com/netease/nimlib/search/model/NIMIndexRecord");
	field_type = env->GetFieldID(class_record, "type", "J");
	field_subtype = env->GetFieldID(class_record, "subtype", "J");
	field_dataid = env->GetFieldID(class_record, "dataid", "J");
	field_id = env->GetFieldID(class_record, "id", "Ljava/lang/String;");
	field_time = env->GetFieldID(class_record, "time", "J");
	field_content = env->GetFieldID(class_record, "content", "Ljava/lang/String;");
	field_count = env->GetFieldID(class_record, "count", "I");
}

#define QUERY_ALL_SQL "select type,subtype,dataid,id,time,content,count(*) from FtsMetaMessage join FtsIndexMessage using(docid) where " \
	"docid in (select docid from FtsIndexMessage where content match ?)" \
	" and type=?" \
	" and status=0" \
	" group by id having max(time)" \
	" order by time desc" \
	" limit ?"

#define QUERY_ID_SQL "select type,subtype,dataid,id,time,content,1 from FtsMetaMessage join FtsIndexMessage using(docid) where " \
	"docid in (select docid from FtsIndexMessage where content match ?)" \
	" and type=?" \
	" and id=?" \
	" and status=0" \
	" order by time desc" \
	" limit ?"

static int record_query_all(sqlite3* db, jlong type, jstring query, jint limit, JNIEnv* env, sqlite3_stmt** pStatement) {
	sqlite3_stmt* statement = NULL;
	int ret = sqlite3_prepare_v2(db, QUERY_ALL_SQL, -1, &statement, 0);
//	log_print(ANDROID_LOG_DEBUG, TAG_NIM_INDEX_OP, "record_query_all: prepare %d", ret);

	if (ret != SQLITE_OK) {
		return ret;
	}

	const char* sQuery = env->GetStringUTFChars(query, NULL);
	sqlite3_bind_text(statement, 1, sQuery, -1, SQLITE_TRANSIENT);
	env->ReleaseStringUTFChars(query, sQuery);

	sqlite3_bind_int64(statement, 2, type);
	sqlite3_bind_int(statement, 3, limit);

	*pStatement = statement;

	return ret;
}

int record_query_id(sqlite3* db, jlong type, jstring query, jint limit, jstring id, JNIEnv* env, sqlite3_stmt** pStatement) {
	sqlite3_stmt* statement = NULL;
	int ret = sqlite3_prepare_v2(db, QUERY_ID_SQL, -1, &statement, 0);
//	log_print(ANDROID_LOG_DEBUG, TAG_NIM_INDEX_OP, "record_query_id: prepare %d", ret);

	if (ret != SQLITE_OK) {
		return ret;
	}

	const char* sQuery = env->GetStringUTFChars(query, NULL);
	sqlite3_bind_text(statement, 1, sQuery, -1, SQLITE_TRANSIENT);
	env->ReleaseStringUTFChars(query, sQuery);

	sqlite3_bind_int64(statement, 2, type);

	const char* sId = env->GetStringUTFChars(id, NULL);
	sqlite3_bind_text(statement, 3, sId, -1, SQLITE_TRANSIENT);
	env->ReleaseStringUTFChars(id, sId);

	sqlite3_bind_int(statement, 4, limit);

	*pStatement = statement;

	return ret;
}

int record_query(sqlite3* db, jlong type, jstring query, jint limit, jstring id, JNIEnv* env, sqlite3_stmt** pStatement) {
	int ret = 0;
	if (id == NULL) {
		return record_query_all(db, type, query, limit, env, pStatement);
	} else {
		return record_query_id(db, type, query, limit, id, env, pStatement);
	}
}

void record_copy(sqlite3_stmt* statement, jobject record, JNIEnv* env) {
    if (sqlite3_column_type(statement, 0) == SQLITE_INTEGER) {
    	env->SetLongField(record, field_type, sqlite3_column_int64(statement, 0));
    }

    if (sqlite3_column_type(statement, 1) == SQLITE_INTEGER) {
    	env->SetLongField(record, field_subtype, sqlite3_column_int64(statement, 1));
    }

    if (sqlite3_column_type(statement, 2) == SQLITE_INTEGER) {
    	env->SetLongField(record, field_dataid, sqlite3_column_int64(statement, 2));
    }

    if (sqlite3_column_type(statement, 3) == SQLITE_TEXT) {
    	env->SetObjectField(record, field_id, env->NewStringUTF((const char*)sqlite3_column_text(statement, 3)));
    }

    if (sqlite3_column_type(statement, 4) == SQLITE_INTEGER) {
    	env->SetLongField(record, field_time, sqlite3_column_int64(statement, 4));
    }

    if (sqlite3_column_type(statement, 5) == SQLITE_TEXT) {
    	env->SetObjectField(record, field_content, env->NewStringUTF((const char*)sqlite3_column_text(statement, 5)));
    }

    if (sqlite3_column_type(statement, 6) == SQLITE_INTEGER) {
    	env->SetIntField(record, field_count, sqlite3_column_int(statement, 6));
    }
}

#define INSERT_INDEX_SQL "INSERT INTO FtsIndexMessage(content) values(?)"

static int record_insert_index(sqlite3* db, jobject record, JNIEnv* env) {
	sqlite3_stmt* statement = NULL;
	int ret = sqlite3_prepare_v2(db, INSERT_INDEX_SQL, -1, &statement, 0);
//	log_print(ANDROID_LOG_DEBUG, TAG_NIM_INDEX_OP, "record_insert_index: prepare %d", ret);

	if (ret != SQLITE_OK) {
		return ret;
	}

	jstring content = (jstring)env->GetObjectField(record, field_content);
	const char* sContent = env->GetStringUTFChars(content, NULL);
	sqlite3_bind_text(statement, 1, sContent, -1, SQLITE_TRANSIENT);
	env->ReleaseStringUTFChars(content, sContent);

	ret = sqlite3_step(statement);
//	log_print(ANDROID_LOG_DEBUG, TAG_NIM_INDEX_OP, "record_insert_index: step %d", ret);

	sqlite3_finalize(statement);

	return ret;
}

#define INSERT_META_SQL "INSERT INTO FtsMetaMessage(docid, type, subtype, dataid, id, time) values(?, ?, ?, ?, ?, ?)"

static int record_insert_meta(sqlite3* db, sqlite3_int64 docid, jobject record, JNIEnv* env) {
	sqlite3_stmt* statement = NULL;
	int ret = sqlite3_prepare_v2(db, INSERT_META_SQL, -1, &statement, 0);
//	log_print(ANDROID_LOG_DEBUG, TAG_NIM_INDEX_OP, "record_insert_meta: prepare %d", ret);

	if (ret != SQLITE_OK) {
		return ret;
	}

	sqlite3_bind_int64(statement, 1, docid);
	sqlite3_bind_int64(statement, 2, env->GetLongField(record, field_type));
	sqlite3_bind_int64(statement, 3, env->GetLongField(record, field_subtype));
	sqlite3_bind_int64(statement, 4, env->GetLongField(record, field_dataid));

	jstring id = (jstring)env->GetObjectField(record, field_id);
	const char* sId = env->GetStringUTFChars(id, NULL);
	sqlite3_bind_text(statement, 5, sId, -1, SQLITE_TRANSIENT);
	env->ReleaseStringUTFChars(id, sId);

	sqlite3_bind_int64(statement, 6, env->GetLongField(record, field_time));

	ret = sqlite3_step(statement);
//	log_print(ANDROID_LOG_DEBUG, TAG_NIM_INDEX_OP, "record_insert_meta: step %d", ret);

	sqlite3_finalize(statement);

	return ret;
}

int record_insert(sqlite3* db, jobject record, JNIEnv* env) {
	int ret = record_insert_index(db, record, env);
	if (ret != SQLITE_DONE) {
		return ret;
	}

	if (sqlite3_changes(db) > 0) {
		sqlite3_int64 docid = sqlite3_last_insert_rowid(db);
		ret = record_insert_meta(db, docid, record, env);
	}

	return ret;
}

int record_bulk_insert(sqlite3* db, jobjectArray records, JNIEnv* env) {
	for (int i = 0; i < env->GetArrayLength(records); i++) {
		jobject record = env->GetObjectArrayElement(records, i);

		int ret = record_insert(db, record, env);

		env->DeleteLocalRef(record);

		if (ret != SQLITE_DONE) {
			return ret;
		}
	}

	return SQLITE_DONE;
}

#define DELETE_INDEX_SQL "DELETE FROM FtsIndexMessage WHERE docid in (SELECT docid FROM FtsMetaMessage WHERE type=? AND dataid=?)"

static int record_delete_index(sqlite3* db, jlong type, jlong dataid, JNIEnv* env) {
	sqlite3_stmt* statement = NULL;
	int ret = sqlite3_prepare_v2(db, DELETE_INDEX_SQL, -1, &statement, 0);
//	log_print(ANDROID_LOG_DEBUG, TAG_NIM_INDEX_OP, "record_delete_index: prepare %d", ret);

	if (ret != SQLITE_OK) {
		return ret;
	}

	sqlite3_bind_int64(statement, 1, type);
	sqlite3_bind_int64(statement, 2, dataid);

	ret = sqlite3_step(statement);
//	log_print(ANDROID_LOG_DEBUG, TAG_NIM_INDEX_OP, "record_delete_index: step %d", ret);

	sqlite3_finalize(statement);

	return ret;
}

#define DELETE_ALL_INDEX_SQL "DELETE FROM FtsIndexMessage"

static int record_delete_all_index(sqlite3* db, jlong type, JNIEnv* env) {
	sqlite3_stmt* statement = NULL;
	int ret = sqlite3_prepare_v2(db, DELETE_ALL_INDEX_SQL, -1, &statement, 0);
//	log_print(ANDROID_LOG_DEBUG, TAG_NIM_INDEX_OP, "record_delete_all_index: prepare %d", ret);

	if (ret != SQLITE_OK) {
		return ret;
	}

	ret = sqlite3_step(statement);
//	log_print(ANDROID_LOG_DEBUG, TAG_NIM_INDEX_OP, "record_delete_all_index: step %d", ret);

	sqlite3_finalize(statement);

	return ret;
}

#define DELETE_META_SQL "DELETE FROM FtsMetaMessage WHERE type=? AND dataid=?"

static int record_delete_meta(sqlite3* db, jlong type, jlong dataid, JNIEnv* env) {
	sqlite3_stmt* statement = NULL;
	int ret = sqlite3_prepare_v2(db, DELETE_META_SQL, -1, &statement, 0);
//	log_print(ANDROID_LOG_DEBUG, TAG_NIM_INDEX_OP, "record_delete_meta: prepare %d", ret);

	if (ret != SQLITE_OK) {
		return ret;
	}

	sqlite3_bind_int64(statement, 1, type);
	sqlite3_bind_int64(statement, 2, dataid);

	ret = sqlite3_step(statement);
//	log_print(ANDROID_LOG_DEBUG, TAG_NIM_INDEX_OP, "record_delete_meta: step %d", ret);

	sqlite3_finalize(statement);

	return ret;
}

#define DELETE_ALL_META_SQL "DELETE FROM FtsMetaMessage"

static int record_delete_all_meta(sqlite3* db, jlong type, JNIEnv* env) {
	sqlite3_stmt* statement = NULL;
	int ret = sqlite3_prepare_v2(db, DELETE_ALL_META_SQL, -1, &statement, 0);
//	log_print(ANDROID_LOG_DEBUG, TAG_NIM_INDEX_OP, "record_delete_all_meta: prepare %d", ret);

	if (ret != SQLITE_OK) {
		return ret;
	}

	ret = sqlite3_step(statement);
//	log_print(ANDROID_LOG_DEBUG, TAG_NIM_INDEX_OP, "record_delete_all_meta: step %d", ret);

	sqlite3_finalize(statement);

	return ret;
}

#define MARK_META_SQL "UPDATE FtsMetaMessage SET status=1 WHERE type=? AND id=?"

static int record_mark_meta(sqlite3* db, jlong type, jstring id, JNIEnv* env) {
	sqlite3_stmt* statement = NULL;
	int ret = sqlite3_prepare_v2(db, MARK_META_SQL, -1, &statement, 0);
//	log_print(ANDROID_LOG_DEBUG, TAG_NIM_INDEX_OP, "record_mark_meta: prepare %d", ret);

	if (ret != SQLITE_OK) {
		return ret;
	}

	sqlite3_bind_int64(statement, 1, type);

	const char* sId = env->GetStringUTFChars(id, NULL);
	sqlite3_bind_text(statement, 2, sId, -1, SQLITE_TRANSIENT);
	env->ReleaseStringUTFChars(id, sId);

	ret = sqlite3_step(statement);
//	log_print(ANDROID_LOG_DEBUG, TAG_NIM_INDEX_OP, "record_mark_meta: step %d", ret);

	sqlite3_finalize(statement);

	return ret;
}

int record_delete(sqlite3* db, jlong type, jlong dataid, jstring id, JNIEnv* env) {
	if (dataid == -1 && id == NULL) {
		int ret = record_delete_all_index(db, type, env);
		if (ret != SQLITE_DONE) {
			return ret;
		}

		return record_delete_all_meta(db, type, env);
	} else if (dataid != -1) {
		int ret = record_delete_index(db, type, dataid, env);
		if (ret != SQLITE_DONE) {
			return ret;
		}

		return record_delete_meta(db, type, dataid, env);
	} else if (id != NULL) {
		return record_mark_meta(db, type, id, env);
	} else {
		return SQLITE_DONE;
	}
}
